using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Threading;

using Microsoft.CSharp;
//TODO: using Microsoft.VisualBasic;

namespace WebServer
{
	public enum CompileStatus
	{
		Initializing,
		Compiling,
		Completed,
		Aborted
	}

	public class CSharpCompiler
	{
		public static readonly string[] DefaultReferences = new string[] { "System.dll", "System.Data.dll", "System.Drawing.dll", "System.Web.dll", "System.Xml.dll" };

		private static volatile string _DefaultInputPath = IOUtility.GetSafeDirectoryPath(Core.BaseDirectory + "\\" + Config.SystemName + "\\Custom\\");
		public static string DefaultInputPath { get { return _DefaultInputPath; } set { _DefaultInputPath = IOUtility.GetSafeDirectoryPath(value); } }

		private static volatile string _DefaultOutputPath = IOUtility.GetSafeDirectoryPath(Core.BaseDirectory + "\\" + Config.SystemName + "\\Output\\");
		public static string DefaultOutputPath { get { return _DefaultOutputPath; } set { _DefaultOutputPath = IOUtility.GetSafeDirectoryPath(value); } }

		private volatile List<string> _References = new List<string>();
		public List<string> References { get { return _References; } }

		private volatile List<string> _FileMasks = new List<string>();
		public List<string> FileMasks { get { return _FileMasks; } }

		private volatile CompileStatus _Status = CompileStatus.Initializing;
		public CompileStatus Status { get { return _Status; } }

		private volatile string _StatusMessage = String.Empty;
		public string StatusMessage { get { return _StatusMessage; } }

		private volatile CSharpCodeProvider _Provider = new CSharpCodeProvider();
		public CSharpCodeProvider Provider
		{
			get
			{
				if (_Provider == null)
					_Provider = new CSharpCodeProvider();

				return _Provider;
			}
		}

		private volatile CompilerParameters _Parameters;
		public CompilerParameters Parameters { get { return _Parameters; } }

		private volatile CompilerResults _Results;
		public CompilerResults Results { get { return _Results; } }

		private volatile string[] _Errors = new string[0];
		public string[] Errors { get { return _Errors; } }

		private volatile DirectoryInfo _InputDirectory;
		public DirectoryInfo InputDirectory { get { return _InputDirectory; } set { _InputDirectory = value; } }

		private volatile DirectoryInfo _OutputDirectory;
		public DirectoryInfo OutputDirectory { get { return _OutputDirectory; } set { _OutputDirectory = value; } }

		private volatile string _OutputFileName = String.Empty;
		public string OutputFileName { get { return _OutputFileName; } set { _OutputFileName = value; } }

		private volatile bool _Debug = false;
		public bool Debug { get { return _Debug; } set { _Debug = value; } }

		public CSharpCompiler(DirectoryInfo input, DirectoryInfo output, string outputFileName)
		{
			_InputDirectory = input;
			_OutputDirectory = IOUtility.EnsureDirectory(output, true);
			_OutputFileName = outputFileName;
		}

		public CSharpCompiler(string inputPath, string outputPath, string outputFileName)
			: this(new DirectoryInfo(IOUtility.GetSafeDirectoryPath(inputPath)), IOUtility.EnsureDirectory(outputPath, true), outputFileName)
		{ }

		public CSharpCompiler(string outputFileName)
			: this(new DirectoryInfo(_DefaultInputPath), new DirectoryInfo(_DefaultOutputPath), outputFileName)
		{ }

		public void AddReference(string name)
		{
			if (!_References.Contains(name))
				_References.Add(name);
		}

		public void AddFileMask(string mask)
		{
			if (!_FileMasks.Contains(mask))
				_FileMasks.Add(mask);
		}

		public void Compile()
		{
			if (_Status == CompileStatus.Compiling)
				return;

			Thread pct = new Thread(this.Init);
			pct.Start();

			while (_Status == CompileStatus.Initializing || _Status == CompileStatus.Compiling)
			{ }

			pct.Join();
		}

		[STAThread]
		private void Init()
		{
			try
			{
				_Status = CompileStatus.Initializing;

				if (!_InputDirectory.Exists)
				{
					_Status = CompileStatus.Aborted;
					_StatusMessage = "Input directory '" + _InputDirectory.FullName + "' does not exist.";
					return;
				}

				List<FileInfo> infos = new List<FileInfo>();

				for (int i = 0; i < _FileMasks.Count; i++)
				{
					foreach (FileInfo file in _InputDirectory.GetFiles(_FileMasks[i], SearchOption.AllDirectories))
					{
						if (!infos.Contains(file))
							infos.Add(file);
					}
				}

				FileInfo[] files = infos.ToArray();
				infos.Clear();

				if (files.Length == 0)
				{
					_Status = CompileStatus.Aborted;
					_StatusMessage = "No files to compile.";
					return;
				}

				List<string> refs = new List<string>();
				List<string> fileNames = new List<string>();

				for (int i = 0; i < files.Length; i++)
				{
					string fName = files[i].FullName;

					if (!String.IsNullOrEmpty(fName))
					{
						if (!fileNames.Contains(fName))
							fileNames.Add(fName);
					}
				}

				for (int i = 0; i < DefaultReferences.Length; i++)
				{
					if (!String.IsNullOrEmpty(DefaultReferences[i]))
					{
						if (!refs.Contains(DefaultReferences[i]))
							refs.Add(DefaultReferences[i]);
					}
				}

				for (int i = 0; i < _References.Count; i++)
				{
					if (!String.IsNullOrEmpty(_References[i]))
					{
						if (!refs.Contains(_References[i]))
							refs.Add(_References[i]);
					}
				}

				FileInfo[] configs = GetConfigFiles();
				DirectoryInfo baseDir = new DirectoryInfo(IOUtility.GetBaseDirectory());

				if (configs != null)
				{
					foreach (FileInfo config in configs)
					{
						string[] asm = GetConfigAssemblies(config);

						for (int i = 0; i < asm.Length; i++)
						{
							if (!String.IsNullOrEmpty(asm[i]))
							{
								if (!File.Exists(IOUtility.GetSafeFilePath(IOUtility.GetBaseDirectory() + "\\" + asm[i], true)))
									continue;

								if (!refs.Contains(asm[i]))
									refs.Add(asm[i]);
							}
						}
					}
				}

				_Status = CompileStatus.Compiling;
				_Parameters =
					new CompilerParameters(refs.ToArray(), IOUtility.GetUnusedFilePath(_OutputDirectory.FullName, _OutputFileName), _Debug);
				_Results = _Provider.CompileAssemblyFromFile(_Parameters, fileNames.ToArray());

				if (_Results.Errors.Count > 0)
				{
					int errorCount = 0, warningCount = 0;

					foreach (CompilerError e in _Results.Errors)
					{
						if (e.IsWarning)
							++warningCount;
						else
							++errorCount;
					}

					_Errors = new string[_Results.Errors.Count];

					for (int e = 0; e < _Results.Errors.Count; e++)
					{
						_Errors[e] = String.Format(
							"[{0}][{1}][{2}]: Line {3}, Column {4}\n{5}",
							_Results.Errors[e].IsWarning ? "Warning" : "Error", _Results.Errors[e].FileName,
							_Results.Errors[e].ErrorNumber, _Results.Errors[e].Line, _Results.Errors[e].Column,
							_Results.Errors[e].ErrorText);
					}

					_StatusMessage = String.Format(
						"Finished compiling with {0} error{1} and {2} warning{3}",
						errorCount, errorCount > 1 ? "s" : "", warningCount, warningCount > 1 ? "s" : "");

					_Status = CompileStatus.Completed;
				}
				else
				{
					_Status = CompileStatus.Completed;
					_StatusMessage = "Finished compiling with no errors or warnings.";
				}
			}
			catch (Exception ex)
			{
				_Status = CompileStatus.Aborted;
				_StatusMessage = ex.Message;
			}
		}

		public FileInfo[] GetConfigFiles()
		{
			FileInfo[] configs = _InputDirectory.GetFiles("*.cfg", SearchOption.AllDirectories);

			if (configs.Length > 0)
				return configs;

			return null;
		}

		private string[] GetConfigAssemblies(FileInfo file)
		{
			List<string> list = new List<string>();

			if (file.Exists)
			{
				string[] lines = File.ReadAllLines(file.FullName, Encoding.Default);

				string content = String.Empty;
				bool inTag = false;
				for (int i = 0; i < lines.Length; i++)
				{
					if (lines[i].StartsWith("[WSCSC]"))
					{
						inTag = true;
						lines[i] = lines[i].Replace("[WSCSC]", String.Empty);
					}
					else if (lines[i].StartsWith("["))
					{
						inTag = false;
						lines[i] = String.Empty;
					}

					if (inTag)
					{
						if (!String.IsNullOrEmpty(lines[i]))
							content += lines[i].Trim();
					}
				}

				string[] split = content.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

				foreach (string assembly in split)
				{
					if (String.IsNullOrEmpty(assembly) || assembly.StartsWith("#"))
						continue;

					list.Add(assembly);
				}
			}

			return list.ToArray();
		}
	}
}